%% -*- coding: utf-8 -*-

%% @doc テンプレート処理系のフロントエンド
%%
-module(templ).
-export([start/0]).
-export([init/0,init/1]).
-export([process/1, process/2, process/4]).
-export([process_file/1, process_file/2, process_file/4]).
-export([read_file/1]).
-export([set_path/1, append_path/1, get_path/0]).

-include_lib("../include/templ.hrl").
-include_lib("../include/templ_delim.hrl").

-define(PATH_MAN, templ_pman).

%% @type templ() = string()
%% @type jsont() = any()

%% @spec init(proplist()|jsont()) -> env()
%% @doc templの初期化処理を行う
%%
init({obj,_}=Ctx) ->
	% JSONタームをプロパティリストに変換
	try templ_json:convert_to_context(Ctx) of
		PL -> init(PL)
	catch
		error:function_clause ->
			throw({{?MODULE,einval},Ctx})
	end;
init([{_,_}|_]=Ctx) ->
	init_pman(),
	E = templ_env:init(Ctx),
	templ_env:store_env(E);
init([]) ->
	init_pman(),
	E = templ_env:init([]),
	templ_env:store_env(E);
init(Ctx) ->
	% JSON文字列をデコード
	case rfc4627:decode(Ctx) of
		{ok,JsonT,_} -> init(JsonT);
		{error,_}    -> 
			throw({{?MODULE,einval},Ctx})
	end.

init() -> init([]).

start() -> init().


%% @spec process(templ()) -> templ()
%% @doc デフォルトのコンテキストとデリミタに基づき、テンプレート処理を行う
%%
process(Templ) when is_list(Templ) ->
	% てんぷら宣言の処理
	case templ_decl:parse(Templ) of
		{PL,TBody} ->
			D = dict:from_list(PL),
			LDlm = case dict:find(?LDKey, D) of
					   {ok,LD} -> LD;
					   error -> ?LDelim
				   end,
			RDlm = case dict:find(?RDKey, D) of
					   {ok,RD} -> RD;
					   error -> ?RDelim
				   end,
			io:format("~p ~p~n", [LDlm,RDlm]),
			templ_env:set_delim(LDlm, RDlm),

			process_body(TBody);
		none ->
			process_body(Templ)
	end;
process(Templ) when is_binary(Templ) ->
	process(binary_to_list(Templ));
process(Templ) ->
	throw({{?MODULE,einval},Templ}).

process_body(Templ) when is_list(Templ) ->
	% 字句解析
	Tokens = templ_scan:string(Templ),

	% 構文解析
	templ_parse_handler:reset_stmt(),
	case templ_parser:parse(Tokens++[{'$end',999}]) of
		{ok,_}      -> none;
		{error,Why} ->
			throw({{?MODULE,einval},Why})
	end,
	ASs = templ_parse_handler:get_stmt(),
	case ASs of
		[AS] ->
			% 中間表現を生成
			IE = templ_ie:transform(AS),

			% 抽象機械上で評価
			templ_am:eval(IE);
		[]   -> [];
		_    ->
			throw({{?MODULE,einval},ASs,"unexpected parse result"})
	end.
	

%% @spec process(templ(), context()) -> templ()
%% @doc 引数のコンテキストとデフォルトのデリミタに基づき、
%%   テンプレート処理を行う
%%
process(Templ, Ctx) ->
	% TODO stacking
	init(Ctx),
	process(Templ).


%% @spec process(templ(), context(), delim(), delim()) -> templ()
%% @doc 引数のコンテキストとデリミタに基づき、テンプレート処理を行う
%%
process(Templ, Ctx, LDelim, RDelim) 
  when is_list(Templ), is_list(LDelim), is_list(RDelim) ->
	init(Ctx),
	templ_env:set_delim(LDelim, RDelim),
	process(Templ);
process(Templ, Ctx, LDelim, RDelim) 
  when is_list(Templ), is_number(LDelim), is_number(RDelim) ->
	process(Templ, Ctx, [LDelim], [RDelim]);
process(Templ, Ctx, LDelim, RDelim) ->
	throw({{?MODULE,einval},[Templ,Ctx,LDelim,RDelim]}).


%% @spec process_file(file()) -> templ()
%% @doc デフォルトのコンテキストとデリミタに基づき、
%% 引数のファイルのテンプレート処理を行う
%%
process_file(File) ->
	Templ = read_file(File),
	process(Templ).

%% @spec process_file(file(), context()) -> templ()
%% @doc 引数のコンテキストとデフォルトのデリミタに基づき、
%% 引数のファイルのテンプレート処理を行う
%%
process_file(File, Ctx) ->
	Templ = read_file(File),
	process(Templ, Ctx).

%% @spec process_file(file(), context(), delim(), delim()) -> templ()
%% @doc 引数のコンテキストとデリミタに基づき、
%% 引数のファイルのテンプレート処理を行う
%%
process_file(File, Ctx, LDelim, RDelim) ->
	Templ = read_file(File),
	process(Templ, Ctx, LDelim, RDelim).


read_file(File) ->
	% パス指定なしで読み込む
	case file:read_file(File) of
		{ok, Bin} -> 
			binary_to_list(Bin);
		{error, _Why} ->
			%% ファイル名に'/'が含まれているか？
			%case string:rchr(File, $/) of
			%	0 -> % 含まれていない
					% 設定パスから探す
					Path = templ_path_manager:get_path(?PATH_MAN),
					read_file(File, Path)
			%	_ ->
			%		throw({{?MODULE,enoent},File})
			%end
	end.

read_file(File, [Dir|Dirs]) ->
	case file:read_file(Dir++[$/]++File) of
		{ok, Bin} -> binary_to_list(Bin);
		{error, _Why} -> read_file(File, Dirs)
	end;
read_file(File, []) ->
	throw({{?MODULE,enoent},File}).


%% @spec init_pman() -> true | none
%% @doc templ_path_managerの初期化処理を行う
%%
init_pman() ->
	case whereis(?PATH_MAN) of
		undefined ->
			{ok, P} = templ_path_manager:start([], []),
			register(?PATH_MAN, P);
		_ -> none
	end.


%% @spec set_path([string()] | string()) -> ok
%% @doc テンプレートファイル読み込みパスを上書き設定する
%%
set_path(Path) ->
	templ_path_manager:set_path(?PATH_MAN, Path).
	
%% @spec append_path([string()] | string()) -> ok
%% @doc テンプレートファイル読み込みパスを追加設定する
%%
append_path(Path) ->
	templ_path_manager:append_path(?PATH_MAN, Path).

%% @spec get_path() -> [string()]
%% @doc 設定されているテンプレートファイル読み込みパスを返す
%%
get_path() ->
	templ_path_manager:get_path(?PATH_MAN).
